package com.web.kdl.config.security.components;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.web.kdl.common.result.CommonResult;
import com.web.kdl.common.result.ReturnCode;
import com.web.kdl.config.security.AuthConstants;
import com.web.kdl.config.security.IgnoredUrlsConfig;
import com.web.kdl.config.security.UserDetailService;
import com.web.kdl.util.JwtTokenUtil;
import com.web.kdl.util.RedisUtils;
import com.web.kdl.util.ServletUtils;
import com.web.kdl.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.web.kdl.config.security.AuthConstants.REDIS_KEY_AUTH_TOKEN;
import static com.web.kdl.config.security.AuthConstants.REDIS_KEY_AUTH_USER_DETAIL;

/**
 * <p>
 *  每次请求过滤token
 * </p>
 * @Author： shulibin
 * @Date： 2025/7/25 11:10
 * @Describe：OncePerRequestFilter 保证每一个请求只通过一次
 */
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final RedisUtils redisUtils;
    private final Long expiration;
    private final IgnoredUrlsConfig ignoredUrlsConfig;

    public JwtAuthenticationFilter(RedisUtils redisUtils, Long expiration, IgnoredUrlsConfig ignoredUrlsConfig) {
        this.redisUtils = redisUtils;
        this.expiration = expiration;
        this.ignoredUrlsConfig = ignoredUrlsConfig;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info("TokenFilter doFilterInternal start");
        
        // 检查是否在白名单中
        String requestPath = request.getServletPath();
        if (isIgnoredUrl(requestPath)) {
            filterChain.doFilter(request, response);
            return;
        }
        
//     1. 获取请求头中得token
        final String token = request.getHeader(AuthConstants.TOKEN_HEADER);
        log.info("Request URL: {}", request.getRequestURL());

//     2. 若请求中未携带token
        // 1.请求头中没有携带token
        if (StrUtil.isBlank(token)) {
            ServletUtils.renderResult(response, new CommonResult<>(ReturnCode.UNAUTHORIZED));
            return;
        }


//     3. 去除token请求头中的前缀
//        final String token = authorization.replace(AuthConstants.TOKEN_PREFIX, "");
        String userId;
//     4. 提供的token异常
        try {
            userId =  JwtTokenUtil.extractUserId(token);
        }catch (Exception e){
//            log.error("YaTokenFilter doFilterInternal 解析jwt异常：{}", e.toString());
            ServletUtils.renderResult(response, new CommonResult<>(ReturnCode.UNAUTHORIZED.getCode(), "凭证异常"));
            return;
        }

//      获取redis中的token
        String redisToken = redisUtils.getString(REDIS_KEY_AUTH_TOKEN + userId);
        if(StrUtil.isBlank(redisToken)){
            ServletUtils.renderResult(response, new CommonResult<>(ReturnCode.UNAUTHORIZED.getCode(), "登录已过期，请重新登录过期。"));
            return;
        }
//      5.提供的token是合法的，但是redis中的token又被使用登录功能重新刷新了一下，导致不一致。
        if(!Objects.equals(redisToken, token)){
            ServletUtils.renderResult(response, new CommonResult<>(ReturnCode.UNAUTHORIZED.getCode(), "账号在别处登陆。"));
            return;
        }
        // token续期
        redisUtils.set(REDIS_KEY_AUTH_TOKEN + userId, token);
//        redisUtils.set(REDIS_KEY_AUTH_TOKEN + userId, token, expiration);
        // 获取用户信息和权限
        String userDetailStr =  redisUtils.getString(REDIS_KEY_AUTH_USER_DETAIL + userId);
        UserDetails userDetails;
        if(Objects.isNull(userDetailStr)){
            userDetails = UserDetailService().loadUserByUsername(userId);
            redisUtils.set(REDIS_KEY_AUTH_USER_DETAIL + userId, JSON.toJSONString(userDetails), 1, TimeUnit.DAYS);
        }else{
            userDetails = initUser(userDetailStr);
        }
            SecurityContextHolder.getContext().setAuthentication(
                    new UsernamePasswordAuthenticationToken(
                            userDetails, userDetails.getPassword(), userDetails.getAuthorities()
                    )
            );
        log.info("TokenFilter doFilterInternal end");
        filterChain.doFilter(request, response);
    }
    
    /**
     * 检查请求路径是否在白名单中
     */
    private boolean isIgnoredUrl(String requestPath) {
        if (ignoredUrlsConfig != null && ignoredUrlsConfig.getUrls() != null) {
            return ignoredUrlsConfig.getUrls().stream()
                    .anyMatch(pattern -> {
                        // 支持通配符匹配
                        if (pattern.endsWith("/**")) {
                            String prefix = pattern.substring(0, pattern.length() - 3);
                            return requestPath.startsWith(prefix);
                        } else if (pattern.equals("/**")) {
                            return true; // 匹配所有路径
                        } else {
                            return requestPath.equals(pattern);
                        }
                    });
        }
        return false;
    }
    
    private UserDetailService UserDetailService(){
        return SpringContextUtils.getBean(UserDetailService.class);
    }

    private AuthUser initUser(String userJsonStr){
        JSONObject userJson = JSON.parseObject(userJsonStr);

        String userId = userJson.getString("userId");
        String username = userJson.getString("username");
        String email = userJson.getString("email");
        String role = userJson.getString("role");
        JSONArray authArray = userJson.getJSONArray("authorities");

        List<GrantedAuthority> authorities = new ArrayList<>(authArray.size());
        for(int i=0; i< authArray.size();i++){
            JSONObject authObj = authArray.getJSONObject(i);
            authorities.add(new SimpleGrantedAuthority(authObj.getString("authority")));
        }
        return new AuthUser(userId, username, "[PROTECTED]", email,role, authorities);
    }
}
